[译文] XI2 食谱(1)
作者:Peter Hutterer
原文发布时间:2009年6月26日
原文来源:http://who-t.blogspot.com/2009/05/xi2-recipes-part-1.html
译者:王旭 ( http://wangxu.me , @gnawux )
翻译时间:2009年9月28日
XI2 (X Input 2)目前已经进入 XServer 的主线了。在接下来的几天里,我将发布出来一组“食谱”,来介绍如何和这些新功能打交道。这里的例子仅仅是一些片段,每部分的完整的示例都将放在这里。
09年7月13日更新:调整到新的 cookie event API。
在第一部分中,我将介绍一些总体的信息,初始化以及事件选择。
为什么需要 XI2?
引入 XI2 的一个重要原因是要将 MPX 并入 X Server。当前的 1.5 版本的 X Input 扩展余地非常有限,很难进行扩展以完全支持 MPX。在 XI 1.5 上编程非常痛苦,所以,我们开始寻找一个替代方案,从而让支持多设备的程序更容易写一些,并且能灵活地应付将来的应用场景。
XI2 的协议时相当保守的,只增加了一些新的请求和事件,但它也留下了很多的发展空间。XI2 和它的 API 与 X 核心 API 的模式非常接近。从客户应用(X Client)的角度看,与 XI 的最大不同在于调用需要附带一个设备ID参数,不需要去打开设备,而事件类型和掩码都是常数(更多的在下面)。
现在,惟一的 XI2 绑定是 Xlib 绑定。尽管如此,我仍鼓励你开始采用 XI2 并思考程序如何使用它。通过现在进行测试,你可以在 2.0 发布之前帮助发现问题和当前版本中的缺陷。当然,在发布前修正 bug 可以惠及所有人。
MPX
MPX允许同时使用多个鼠标指针和键盘焦点。这将有助于引领相当时髦的用户界面——双手操作、多人操作……所有你能说出来的。它还抛弃了当前 GUI 设计的一些假设,但我将在将来再去谈这些。
MPX引入了一个显式的主/从设备层次关系。最简单的记住这个层次关系的方式是:物理设备是从设备,而鼠标指针或键盘焦点是主设备。
从设备附着到一个主设备上,每当从设备产生一个事件,这个事件将被通过主设备路由到客户应用上。主设备总是成对出现(一个指针,一个键盘焦点)。
这样,一个常见的配置可能是一个笔记本有四个主设备(两个键盘焦点和两个鼠标指针),触控板和内建键盘控制第一对主设备,一个USB无线键鼠套装控制第二对主设备。标准配置是有一堆主设备,所有设备都附着到这对主指针和焦点上。这个方案意外地(或无意外地)和我们自从 X Server 1.4 的配置是一样的。这也意味着除非你建立一对新的主设备,否则 MPX 根本就不可见。
我将在后面的文章里更多的介绍 MPX,从大多数客户应用的角度看,主设备是惟一需要关心的问题,只有配置工具和一些其他的特殊程序才真的需要去关心从设备。
XI2 初始化
一个典型的 XI2 程序如下面这么开头:
/* Connect to the X server */
Display *dpy = XOpenDisplay(NULL);
/* XInput Extension available? */
int opcode, event, error;
if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) {
printf("X Input extension not available.\n");
return -1;
}
/* Which version of XI2? We support 2.0 */
int major = 2, minor = 0;
if (XIQueryVersion(dpy, &major, &minor) == BadRequest) {
printf("XI2 not available. Server supports %d.%d\n", major, minor);
return -1;
}
首先,客户应用连接 X Server,之后询问可用的 XI 扩展的版本。XQueryExtension 不仅告诉我们 XI 扩展是否存在,也会返回扩展的 opcode。这个 opcode 在事件处理时需要用到(所有的 XI2 事件都用到了这个 opcode)。这个 opcode 是在 X Server 启动时设置的,所以,在跨 X Server 的应用中,你不能把它当做个常量。
最后,我们宣布我们支持 XI 2.0,服务器返回它支持的版本。 XIQueryVersion 是一个纯 XI2 调用,即使你在一个不支持 XI2 的服务器上调用,它的实现也不会返回一个 BadRequest 错误(这是在 Xlib 中的实现,所以如果你使用xcb,这个行为可能会不同)。
XIQueryVersion 不仅返回支持的版本,X Server 也会存储你的客户应用支持的版本。随着 XI2 的进程,使用这个调用变得非常重要,服务器将根据支持的版本来区别对待不同的客户应用。
事件的选择
初始化GUI之后,客户应用通常需要选择事件。这一工作可以通过 XISelectEvents 实现。
XIEventMask eventmask;unsigned char mask[1] = { 0 }; /* the actual mask */
eventmask.deviceid = 2;
eventmask.mask_len = sizeof(mask); /* always in bytes */
eventmask.mask = mask;
/* now set the mask */
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_Motion);
XISetMask(mask, XI_KeyPress);
/* select on the window */
XISelectEvents(display, window, &eventmask, 1);
XIEventMask 定义了一个设备的掩码。掩码定义为(1 事件类型),匹配位必须在 eventmask.mask 域中设置。eventmask.mask 的尺寸可以是任意大小的,倡导可以满足所有需要设置的掩码。XI_LASTEVENT 定义为当前 XI2 协议中的最高的事件类型,所以你可以使用它来确定需要设置的掩码的位数。在本例中,我们只需要6位,所以一个字节就够了。
XISelectEvents 携带了多个事件掩码,所以,你可以一次提交多个事件的选择(比如每个对应一个设备)。
如上所示,每个 XIEventMask 都带有一个设备ID。要么是一个设备的数字ID,或是 XIAllDevices 或 XIAllMasterDevices 这样的特殊 ID。如果你选择 XIAllDevices 的事件掩码,所有的设备都会送来选择的事件。XIAllMasterDevices 也是一样,但只是所有的主设备。
XIAllDevices 和 XIAllMasterDevices 的事件掩码是特定设备事件掩码的一个补充。例如,你可以选择所有设备的按下事件,所有主设备的松开事件,以及设备2的移动事件,设备2将会向客户应用报告所有三类事件(有效掩码是按位或的)。
并且,XIAllDevices 和 XIAllMasterDevices 对所有设备都会生效,即使设备在客户应用选择事件之后才加入也会生效。所以,你只要设置事件掩码一次,无需关心当前有多少设备可用,将来还会进入多少设备。
检查事件的非常简单:
XEvent event;
XNextEvent(display, &event);
if (ev.xcookie.type == GenericEvent &&
ev.xcookie.extension == opcode &&
XGetEventData(dpy, &ev.xcookie))
{
switch(ev.xcookie.evtype)
{
case XI_ButtonPress:
case XI_Motion:
case XI_KeyPress:
do_something(ev.xcookie.data);
break;
}
}
XFreeEventData(dpy, &ev.xcookie);
对每个事件的数据进行更加细节的分析将在后面的文章中被介绍。
注意,你需要检查 XI 扩展的 opcode,和 XQueryExtension(3) 返回的 opcode 进行比较(如前所述)。
就这么简单,有了这些信息,你已经可以开始写简单的监听所有设备的事件的程序了。在下一部分,我将介绍如何列出输入设备,并监听主/从设备层次结构发生的变化。